package net.jalbum.filters;

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.Serializable;

public class ResizableObject implements MouseMotionListener, MouseListener, KeyListener, Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 5798630994588019554L;
	protected double X1;
	protected double Y1;
	protected double X2;
	protected double Y2;
	protected double OutX1;
	protected double OutX2;
	protected double OutY1;
	protected double OutY2;
	protected double OffsetX;
	protected double OffsetY;
	protected int width;
	protected int height;
	protected int state;
	protected int position;
	protected boolean initial = true;
	protected boolean repaintOnCursorChange = true;
	protected String name = "Resizable Object";
	public static final int MAKE = 0;
	public static final int MOVE = 1;
	public static final int RESIZE_NW = 2;
	public static final int RESIZE_NE = 3;
	public static final int RESIZE_SE = 4;
	public static final int RESIZE_SW = 5;
	public static final int RESIZE_N = 6;
	public static final int RESIZE_E = 7;
	public static final int RESIZE_S = 8;
	public static final int RESIZE_W = 9;
	public static final int NOTHING = 10;
	protected Color color = new Color(255, 255, 255, 127);
	protected ResizableObjectPanel parentPanel;

	// Variables for settings
	protected int handlesize = 6;

	public ResizableObject() {
		this(100, 100);
	}

	public ResizableObject(final int width, final int height) {
		this.width = width;
		this.height = height;
	}

	public ResizableObject(final int width, final int height, final ResizableObjectPanel parentPanel) {
		this(width, height);
		this.parentPanel = parentPanel;
	}

	public void setParentPanel(final ResizableObjectPanel parentPanel) {
		this.parentPanel = parentPanel;
	}

	public void paint(final Graphics g) {
		if (!this.initial) {
			this.checkDimensions(false);

			g.setColor(this.color);
			// draw the rectangular outlines of the object
			g.drawRect((int) this.OutX1 * (this.width - 1), (int) this.OutY1 * (this.height - 1), (int) (this.OutX2 - this.OutX1) * (this.width - 1), (int) (this.OutY2 - this.OutY1) * (this.width - 1));

			// draw the handles if the mouse is inside or near the object
			this.paintHandles(g);
		}
	}

	protected void paintHandles(final Graphics g) {
		// check mouse position
		if (this.position == ResizableObject.MOVE || this.position == ResizableObject.RESIZE_NW || this.position == ResizableObject.RESIZE_NE || this.position == ResizableObject.RESIZE_SE || this.position == ResizableObject.RESIZE_SW || this.position == ResizableObject.RESIZE_N
				|| this.position == ResizableObject.RESIZE_E || this.position == ResizableObject.RESIZE_S || this.position == ResizableObject.RESIZE_W) {

			// calculate the positions as pixel size
			final int intX1 = (int) (this.OutX1 * (this.width - 1));
			final int intX2 = (int) (this.OutX2 * (this.width - 1));
			final int intY1 = (int) (this.OutY1 * (this.height - 1));
			final int intY2 = (int) (this.OutY2 * (this.height - 1));

			// paint the white background for the corner handles
			g.setColor(Color.WHITE);
			g.fillRect(intX1 - this.handlesize / 2, intY1 - this.handlesize / 2, this.handlesize, this.handlesize);
			g.fillRect(intX1 - this.handlesize / 2, intY2 - this.handlesize / 2, this.handlesize, this.handlesize);
			g.fillRect(intX2 - this.handlesize / 2, intY1 - this.handlesize / 2, this.handlesize, this.handlesize);
			g.fillRect(intX2 - this.handlesize / 2, intY2 - this.handlesize / 2, this.handlesize, this.handlesize);

			// paint the black frame for the corner handles
			g.setColor(Color.BLACK);
			g.drawRect(intX1 - this.handlesize / 2, intY1 - this.handlesize / 2, this.handlesize, this.handlesize);
			g.drawRect(intX1 - this.handlesize / 2, intY2 - this.handlesize / 2, this.handlesize, this.handlesize);
			g.drawRect(intX2 - this.handlesize / 2, intY1 - this.handlesize / 2, this.handlesize, this.handlesize);
			g.drawRect(intX2 - this.handlesize / 2, intY2 - this.handlesize / 2, this.handlesize, this.handlesize);
		}
	}

	protected int checkState(final int x, final int y) {
		// check close to which corner or side the mouse is and chance the state accordingly

		// calculate the positions as pixel size
		final int intX1 = (int) (this.OutX1 * (this.width - 1));
		final int intX2 = (int) (this.OutX2 * (this.width - 1));
		final int intY1 = (int) (this.OutY1 * (this.height - 1));
		final int intY2 = (int) (this.OutY2 * (this.height - 1));

		// upper left corner
		if (x > intX1 - 5 && x < intX1 + 5 && y > intY1 - 5 && y < intY1 + 5) {
			return ResizableObject.RESIZE_NW;
			// upper right corner
		} else if (x > intX2 - 5 && x < intX2 + 5 && y > intY1 - 5 && y < intY1 + 5) {
			return ResizableObject.RESIZE_NE;
			// lower right corner
		} else if (x > intX2 - 5 && x < intX2 + 5 && y > intY2 - 5 && y < intY2 + 5) {
			return ResizableObject.RESIZE_SE;
			// lower left corner
		} else if (x > intX1 - 5 && x < intX1 + 5 && y > intY2 - 5 && y < intY2 + 5) {
			return ResizableObject.RESIZE_SW;
			// upper side
		} else if (x > intX1 && x < intX2 && y > intY1 - 5 && y < intY1 + 5) {
			return ResizableObject.RESIZE_N;
			// right side
		} else if (x > intX2 - 5 && x < intX2 + 5 && y > intY1 && y < intY2) {
			return ResizableObject.RESIZE_E;
			// lower side
		} else if (x > intX1 && x < intX2 && y > intY2 - 5 && y < intY2 + 5) {
			return ResizableObject.RESIZE_S;
			// left side
		} else if (x > intX1 - 5 && x < intX1 + 5 && y > intY1 && y < intY2) {
			return ResizableObject.RESIZE_W;
			// inside the box
		} else if (x > intX1 && x < intX2 && y > intY1 && y < intY2) {
			if (this.initial) {
				return ResizableObject.MAKE;
			} else {
				return ResizableObject.MOVE;
			}
			// outside the box but within image area
		} else if (x <= this.width - 1 && y >= 0 && y <= this.height - 1) {
			return ResizableObject.MAKE;
			// outside the image area or with disabled possibility to make a new box
		} else {
			return ResizableObject.NOTHING;
		}
	}

	protected void checkDimensions(final boolean changeOriginal) {
		// make sure that the lowest coordinates are always the left/top ones
		if (this.X1 > this.X2) {
			this.OutX2 = this.X1;
			this.OutX1 = this.X2;
		} else {
			this.OutX1 = this.X1;
			this.OutX2 = this.X2;
		}

		if (this.Y1 > this.Y2) {
			this.OutY2 = this.Y1;
			this.OutY1 = this.Y2;
		} else {
			this.OutY1 = this.Y1;
			this.OutY2 = this.Y2;
		}

		// check dimensions
		if (this.OutX1 < 0) {
			this.OutX1 = 0;
		}
		if (this.OutX1 > 1) {
			this.OutX1 = 1;
		}
		if (this.OutX2 < 0) {
			this.OutX2 = 0;
		}
		if (this.OutX2 > 1) {
			this.OutX2 = 1;
		}
		if (this.OutY1 < 0) {
			this.OutY1 = 0;
		}
		if (this.OutY2 < 0) {
			this.OutY2 = 0;
		}
		if (this.OutY1 > 1) {
			this.OutY1 = 1;
		}
		if (this.OutY2 > 1) {
			this.OutY2 = 1;
		}

		// is called when the mouse is released. In order to ensure smooth drag and drop for resizing the (possibly wrong positioned) coordinates are needed
		if (changeOriginal) {
			this.X1 = this.OutX1;
			this.X2 = this.OutX2;
			this.Y1 = this.OutY1;
			this.Y2 = this.OutY2;
		}

	}

	protected void changeMousePosition(final MouseEvent arg0) {
		// a method that is called by dragMouse and releaseMouse to draw a current image of the box if it is moved or resized

		// calculate the mouse input for the zoomed image
		final int inputX = arg0.getX();
		final int inputY = arg0.getY();

		if (this.state == ResizableObject.MOVE) {
			this.X1 = this.X1 - this.OffsetX + inputX / (double) (this.width - 1);
			this.X2 = this.X2 - this.OffsetX + inputX / (double) (this.width - 1);
			this.OffsetX = inputX / (double) (this.width - 1);

			this.Y1 = this.Y1 - this.OffsetY + inputY / (double) (this.height - 1);
			this.Y2 = this.Y2 - this.OffsetY + inputY / (double) (this.height - 1);
			this.OffsetY = inputY / (double) (this.height - 1);

		} else if (this.state == ResizableObject.MAKE) {
			this.X2 = inputX / (double) (this.width - 1);
			this.Y2 = inputY / (double) (this.height - 1);
		} else if (this.state == ResizableObject.RESIZE_NW) {
			this.X1 = inputX / (double) (this.width - 1);
			this.Y1 = inputY / (double) (this.height - 1);
		} else if (this.state == ResizableObject.RESIZE_NE) {
			this.X2 = inputX / (double) (this.width - 1);
			this.Y1 = inputY / (double) (this.height - 1);
		} else if (this.state == ResizableObject.RESIZE_SE) {
			this.X2 = inputX / (double) (this.width - 1);
			this.Y2 = inputY / (double) (this.height - 1);
		} else if (this.state == ResizableObject.RESIZE_SW) {
			this.X1 = inputX / (double) (this.width - 1);
			this.Y2 = inputY / (double) (this.height - 1);
		} else if (this.state == ResizableObject.RESIZE_N) {
			this.Y1 = inputY / (double) (this.height - 1);
		} else if (this.state == ResizableObject.RESIZE_E) {
			this.X2 = inputX / (double) (this.width - 1);
		} else if (this.state == ResizableObject.RESIZE_S) {
			this.Y2 = inputY / (double) (this.height - 1);
		} else if (this.state == ResizableObject.RESIZE_W) {
			this.X1 = inputX / (double) (this.width - 1);
		}
	}

	protected void setMouseCursor(final MouseEvent arg0) {
		// set the mouse cursor according to the current position
		this.position = this.checkState(arg0.getX(), arg0.getY());
		final Cursor old = this.parentPanel.getCursor();
		if (this.position == ResizableObject.MAKE) {
			this.parentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
		} else if (this.position == ResizableObject.MOVE) {
			this.parentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
		} else if (this.position == ResizableObject.RESIZE_NW) {
			this.parentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
		} else if (this.position == ResizableObject.RESIZE_NE) {
			this.parentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
		} else if (this.position == ResizableObject.RESIZE_SE) {
			this.parentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR));
		} else if (this.position == ResizableObject.RESIZE_SW) {
			this.parentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR));
		} else if (this.position == ResizableObject.RESIZE_N) {
			this.parentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
		} else if (this.position == ResizableObject.RESIZE_E) {
			this.parentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
		} else if (this.position == ResizableObject.RESIZE_S) {
			this.parentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));
		} else if (this.position == ResizableObject.RESIZE_W) {
			this.parentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
		} else {
			this.parentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
		}
		if (old != this.parentPanel.getCursor()) {
			this.parentPanel.renderPreview();
		}
	}

	public void updateParent() {
		this.parentPanel.updateController(this.OutX1, this.OutY1, this.OutX2, this.OutY2);
	}

	public void update(final double x1, final double y1, final double x2, final double y2) {
		this.X1 = x1;
		this.Y1 = y1;
		this.X2 = x2;
		this.Y2 = y2;
		this.checkDimensions(true);
	}

	public void mousePressed(final MouseEvent arg0) {
		final int inputX = arg0.getX();
		final int inputY = arg0.getY();

		this.state = this.checkState(inputX, inputY);

		if (this.state == ResizableObject.MOVE) {
			// remember from where the user started to move the object
			this.OffsetX = inputX / (double) (this.width - 1);
			this.OffsetY = inputY / (double) (this.height - 1);
		} else if (this.state == ResizableObject.MAKE) {
			// make a new object
			this.X1 = inputX / (double) (this.width - 1);
			this.Y1 = inputY / (double) (this.height - 1);
			this.X2 = inputX / (double) (this.width - 1);
			this.Y2 = inputY / (double) (this.height - 1);
		}
		this.initial = false;
		this.changeMousePosition(arg0);
		this.checkDimensions(true);
		this.updateParent();

	}

	public void mouseDragged(final MouseEvent arg0) {
		// update the mouse position and repaint the scroll pane of the parent element to avoid display mistakes
		this.changeMousePosition(arg0);
		this.checkDimensions(false);
		this.updateParent();

	}

	public void mouseReleased(final MouseEvent arg0) {
		// update the mouse position and repaint the scroll pane of the parent element to avoid display mistakes
		this.changeMousePosition(arg0);
		// save the changed mouse positions as they are displayed
		this.checkDimensions(true);
		this.state = ResizableObject.NOTHING;
		this.updateParent();

	}

	public void mouseMoved(final MouseEvent arg0) {
		this.setMouseCursor(arg0);
	}

	public void mouseEntered(final MouseEvent arg0) {
		this.setMouseCursor(arg0);

	}

	public void mouseClicked(final MouseEvent arg0) {
	}

	public void mouseExited(final MouseEvent arg0) {
		this.parentPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
	}

	// give out the width of the object
	public double getObjectWidth() {
		return this.OutX2 - this.OutX1;
	}

	// give out the height of the object
	public double getObjectHeight() {
		return this.OutY2 - this.OutY1;
	}

	public void keyPressed(final KeyEvent arg0) {

		// move the object by using the arrow keys
		if (arg0.getKeyCode() == KeyEvent.VK_KP_LEFT || arg0.getKeyCode() == KeyEvent.VK_LEFT) {
			if (this.X1 > 0) {
				this.X1 = this.X1 - (1 / (double) (this.width - 1));
				this.X2 = this.X2 - (1 / (double) (this.width - 1));
			}
		}
		if (arg0.getKeyCode() == KeyEvent.VK_KP_UP || arg0.getKeyCode() == KeyEvent.VK_UP) {
			if (this.Y1 > 0) {
				this.Y1 = this.Y1 - (1 / (double) (this.height - 1));
				this.Y2 = this.Y2 - (1 / (double) (this.height - 1));
			}
		}
		if (arg0.getKeyCode() == KeyEvent.VK_KP_RIGHT || arg0.getKeyCode() == KeyEvent.VK_RIGHT) {
			if (this.X2 < 1) {
				this.X1 = this.X1 + (1 / (double) (this.width - 1));
				this.X2 = this.X2 + (1 / (double) (this.width - 1));
			}
		}
		if (arg0.getKeyCode() == KeyEvent.VK_KP_DOWN || arg0.getKeyCode() == KeyEvent.VK_DOWN) {
			if (this.Y2 < 1) {
				this.Y1 = this.Y1 + (1 / (double) (this.height - 1));
				this.Y2 = this.Y2 + (1 / (double) (this.height - 1));
			}
		}
		this.checkDimensions(true);
		this.updateParent();
	}

	public void keyTyped(final KeyEvent arg0) {
	}

	public void keyReleased(final KeyEvent arg0) {
	}

	public double getX1() {
		return this.X1;
	}

	public void setX1(final double x1) {
		this.X1 = x1;
	}

	public double getY1() {
		return this.Y1;
	}

	public void setY1(final double y1) {
		this.Y1 = y1;
	}

	public double getX2() {
		return this.X2;
	}

	public void setX2(final double x2) {
		this.X2 = x2;
	}

	public double getY2() {
		return this.Y2;
	}

	public void setY2(final double y2) {
		this.Y2 = y2;
	}

	public void setWidth(final int width) {
		this.width = width;
	}

	public void setHeight(final int height) {
		this.height = height;
	}

	public void setInitial(final boolean initial) {
		this.initial = initial;
	}

	public boolean isInitial() {
		return this.initial;
	}

	public Color getColor() {
		return this.color;
	}

	public void setColor(final Color color) {
		this.color = color;
	}

	public String getName() {
		return this.name;
	}

	public void setName(final String name) {
		this.name = name;
	}

	public double getOutX1() {
		return this.OutX1;
	}

	public double getOutX2() {
		return this.OutX2;
	}

	public double getOutY1() {
		return this.OutY1;
	}

	public double getOutY2() {
		return this.OutY2;
	}

	public int getState() {
		return this.state;
	}

	public void setState(final int state) {
		this.state = state;
		this.position = state;
	}

	public double getOffsetX() {
		return this.OffsetX;
	}

	public void setOffsetX(final double offsetX) {
		this.OffsetX = offsetX;
	}

	public double getOffsetY() {
		return this.OffsetY;
	}

	public void setOffsetY(final double offsetY) {
		this.OffsetY = offsetY;
	}

	public int getPosition() {
		return this.position;
	}

	public void setPosition(final int position) {
		this.position = position;
	}

	public boolean isRepaintOnCursorChange() {
		return this.repaintOnCursorChange;
	}

	public void setRepaintOnCursorChange(final boolean repaintOnCursorChange) {
		this.repaintOnCursorChange = repaintOnCursorChange;
	}

	public int getHandlesize() {
		return this.handlesize;
	}

	public void setHandlesize(final int handlesize) {
		this.handlesize = handlesize;
	}

	public int getWidth() {
		return this.width;
	}

	public int getHeight() {
		return this.height;
	}

	public ResizableObjectPanel getParentPanel() {
		return this.parentPanel;
	}

	public void setOutX1(final double outX1) {
		this.OutX1 = outX1;
	}

	public void setOutX2(final double outX2) {
		this.OutX2 = outX2;
	}

	public void setOutY1(final double outY1) {
		this.OutY1 = outY1;
	}

	public void setOutY2(final double outY2) {
		this.OutY2 = outY2;
	}

}
